package com.opennews.openplatform

import com.opennews.openplatform.constants.HttpHeaderConstant
import com.opennews.openplatform.constants.ServerCodeConstant
import com.opennews.openplatform.constants.ServerError
import com.opennews.openplatform.security.AuthRequestValidator
import com.opennews.openplatform.security.MyClaims
import com.opennews.openplatform.security.ParsedToken
import grails.artefact.controller.RestResponder
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.http.HttpStatus

import javax.servlet.http.HttpServletResponse

@Slf4j
@CompileStatic
abstract class BaseController implements RestResponder {
    AuthRequestValidator authRequestValidator

    // region: Declarative Controller Exception Handling.
    // All related methods must be public.
    // Details go to http://docs.grails.org/snapshot/guide/single.html#controllerExceptionHandling
    def handleValidationExceptionn(grails.validation.ValidationException err) {
        // 406 NOT_ACCEPTABLE
        // SystemLog.logError([
        //     type: SystemErrorLogTypeConstants.SERVER,
        //     source: "BaseController",
        //     sourceInfo: "handleValidationExceptionn",
        //     error: err,
        //     createdBy: getUserInfo()?.user.id
        // ])

        log.error("method: handleValidationExceptionn", err)
        sendStatusResponse(HttpStatus.NOT_ACCEPTABLE)
    }

    def handleDataIntegrityViolationException(org.springframework.dao.DataIntegrityViolationException err) {
        // 422 UNPROCESSABLE_ENTITY
        // SystemLog.logError([
        //     type: SystemErrorLogTypeConstants.SERVER,
        //     source: "BaseController",
        //     sourceInfo: "handleDataIntegrityViolationException",
        //     error: err,
        //     createdBy: getUserInfo()?.user.id
        // ])

        log.error("method: handleDataIntegrityViolationException", err)
        sendErrorResponse(HttpServletResponse.SC_BAD_REQUEST, ServerCodeConstant.BAD_REQUEST, ServerError.ERROR_ENTITY_IN_USE, request.getRequestURI().trim())
    }

    def handleDuplicateKeyException(org.springframework.dao.DuplicateKeyException err) {
        // 409 CONFLICT
        // SystemLog.logError([
        //     type: SystemErrorLogTypeConstants.SERVER,
        //     source: "BaseController",
        //     sourceInfo: "handleDuplicateKeyException",
        //     error: err,
        //     createdBy: getUserInfo()?.user.id
        // ])

        log.error("method: handleDuplicateKeyException", err)
        sendStatusResponse(HttpStatus.CONFLICT)
    }

    def handleOptimisticLockingFailureException(org.springframework.dao.OptimisticLockingFailureException err) {
        // 423 LOCKED
        // SystemLog.logError([
        //     type: SystemErrorLogTypeConstants.SERVER,
        //     source: "BaseController",
        //     sourceInfo: "handleOptimisticLockingFailureException",
        //     error: err,
        //     createdBy: getUserInfo()?.user.id
        // ])

        log.error("method: handleOptimisticLockingFailureException", err)
        sendStatusResponse(HttpStatus.LOCKED)
    }

    // def handleDomainNotAcceptableException(openplatform.utils.exception.DomainNotAcceptableException err) {
    //     SystemLog.logError([
    //         type: SystemErrorLogTypeConstants.SERVER,
    //         source: "BaseController",
    //         sourceInfo: "handleDomainNotAcceptableException",
    //         error: err,
    //         createdBy: getUserInfo()?.user.id
    //     ])
    //
    //     log.error("method: handleDomainNotAcceptableException", err)
    //     render(status: NOT_ACCEPTABLE, text: err.message)
    // }

    // def handleDomainNotFoundException(openplatform.utils.exception.DomainNotFoundException err) {
    //     SystemLog.logError([
    //         type: SystemErrorLogTypeConstants.SERVER,
    //         source: "BaseController",
    //         sourceInfo: "handleDomainNotAcceptableException",
    //         error: err,
    //         createdBy: getUserInfo()?.user.id
    //     ])
    //
    //     log.error("method: handleDomainNotAcceptableException", err)
    //     render(status: HttpStatus.NOT_FOUND)
    // }

    /**
     * Handle all unexpected and unhandled general expetion, hide any detail exception info to client side.
     * @param err : The general exception instance
     * @return
     */
    def handleUnexpectedGeneralException(Exception err) {
        // 400 BAD_REQUEST
        // SystemLog.logError([
        //     type: SystemErrorLogTypeConstants.SERVER,
        //     source: "BaseController",
        //     sourceInfo: "handleUnexpectedGeneralException",
        //     error: err,
        //     createdBy: getUserInfo()?.user?.userCode
        // ])

        log.error("method: handleUnexpectedGeneralException", err)
        sendStatusResponse(HttpStatus.BAD_REQUEST)
    }

    //endregion

    /**
     * Gets claims from token in header.*/
    protected ParsedToken getToken() {
        // Gets authorization from request header.
        // The string should be like below example:
        // Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDU3ODIxNzUsImlhdCI6MTYwNTc3ODU3NSwiaWQiOiJ1c2VyLWlkIiwidXNlcm5hbWUiOiJIdWkiLCJyb2xlcyI6WyJBZG1pbiJdfQ.tKdhYNyeqOS_ITIV_F0sKT6N-NBFjxMkPyXAkzT43WQ
        def authorizationHeader = request.getHeader(HttpHeaderConstant.HTTP_HEADER_AUTHORIZATION)?.trim()

        // Validates token string to see if it is a valid access token and it does not expire yet.
        return authRequestValidator.checkAuthorizationHeader(authorizationHeader)
    }

    /**
     * Gets claims from token.
     * @return
     */
    protected MyClaims getClaims() {
        return getToken().claims
    }

    protected def sendDataResponse(payload) {
        respond([success: true,
                 payload: payload], formats: ["json", "xml"])
    }

    protected def sendUnsuccessResponse(payload) {
        respond([success: false,
                 payload: payload], formats: ["json", "xml"])
    }

    /**
     * Sends http status code to client as request response.
     * @param httpStatus : The http status which will be sent to client.
     */
    protected def sendStatusResponse(HttpStatus httpStatus) {
        // response.status = httpStatus.value()

        respond([:], status: httpStatus)
    }

    /**
     * Sends http error to client as request response.
     * Example:
     * {
     *      success: false,
     *      timestamp: 1642586379,
     *      status: 401,
     *      error: "UNAUTHORIZED",
     *      message: "ERROR_USER_ACCOUNT_NOT_AUTHORIZED",
     *      path: "/family-expense-api/user"
     * }
     *
     * @param httpStatus : Http status.
     * @param error : String to identify error.
     * @param message : String of more specific message.
     * @param path : String of request url path.
     */
    protected void sendErrorResponse(int httpStatus, String error, String message, String path) {
        SharedUtil.sendErrorResponse(response, httpStatus, error, message, path)
    }

    /**
     * Sends file stream to client as download.
     * @param filename : The filename will be shown to client for download.
     * @param bytes : The file content in byte array.
     * @param contentType : The string of the http content type. Default value is "application/octet-stream".
     */
    protected void sendFileResponse(String filename, byte[] bytes, String contentType = "application/octet-stream") {
        response.setContentType(contentType)
        response.setHeader("Content-disposition", "attachment;filename=${filename}")
        response.outputStream << bytes
    }

    /**
     * Gets the client request body data and returns as Map format.
     * @return: The map instance.
     */
    protected Map<String, ?> getRequestBodyData() {
        return SharedUtil.getRequestBodyData(request)
    }

    /**
     * Gets client request ip address
     * @return
     */
    protected String getRequstIpAdress() {
        final String LOCALHOST_IPV4 = "127.0.0.1"
        final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1"
        final unknownStatus = "unknown"

        String ipAddress = request.getHeader("X-Forwarded-For")

        if (SharedUtil.isNullOrEmpty(ipAddress) || unknownStatus.equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP")
        }

        if (SharedUtil.isNullOrEmpty(ipAddress) || unknownStatus.equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP")
        }

        if (SharedUtil.isNullOrEmpty(ipAddress) || unknownStatus.equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr()

            if (LOCALHOST_IPV4.equals(ipAddress) || LOCALHOST_IPV6.equals(ipAddress)) {
                try {
                    InetAddress inetAddress = InetAddress.getLocalHost()
                    ipAddress = inetAddress.getHostAddress()
                } catch (UnknownHostException e) {
                    e.printStackTrace()
                }
            }
        }

        if (!SharedUtil.isNullOrEmpty(ipAddress) && ipAddress.length() > 15 && ipAddress.indexOf(",") > 0) {
            ipAddress = ipAddress.substring(0, ipAddress.indexOf(","))
        }

        return ipAddress
    }
}